package nl.saxion.act.bus;

import java.util.ArrayList;
import java.util.List;

import nl.saxion.act.gameframework.ActiveActor;
import nl.saxion.act.gameframework.Actor;
import nl.saxion.act.gameframework.World;
import nl.saxion.act.gameframework.WorldView;
import nl.saxion.act.logging.Assert;
import nl.saxion.act.logging.EasyLog;
import android.graphics.Canvas;
import android.graphics.Rect;

public class BusActor extends ActiveActor {
	public  static final double MAXIMUM_TE_RIJDEN_AFSTAND_PER_STAP = 1;
	private static final float  GAME_TIME_STEP_SIZE                = 0.01f;
	private static final int    DEFAULT_WACHTTIJD                  = 50;
	private static final long   MAXIMUM_UPDATE_TIME_IN_MILLIS      = 20;

	private Bus                   bus;
	private Route                 route;
	private AbstracteBuschauffeur buschauffeur;
	private Locatie               locatie  = new Locatie( 0, 0 );
	private float                 lastTime = 0;

	enum BusCommands {
		START,
		STOP,
		VERSNEL,
		REM,
		OPEN_DEUR,
		SLUIT_DEUR,
		RIJ,
		STAP_IN,
		STAP_UIT,
		WACHT,
		HALTE
	};
	ArrayList<BusCommands> commands       = new ArrayList<BusCommands>();
	ArrayList<BusCommands> halteCommands  = new ArrayList<BusCommands>();
	ArrayList<HalteProxy>  haltes         = new ArrayList<HalteProxy>();
	ArrayList<Reiziger>    reizigers      = new ArrayList<Reiziger>();
	ArrayList<Locatie>     locaties       = new ArrayList<Locatie>();

	public BusActor( int x, int y , Buswereld world, Bus eenBus, AbstracteBuschauffeur eenBuschauffeur ) {
		super( new Rect( 0, 0, 0, 0 ) );  // placeholder, only the bus will be drawn
		Assert.check( eenBus          != null, "geen bus meegegeven (null)"          );
		Assert.check( eenBuschauffeur != null, "geen buschauffeur meegegeven (null)" );
		bus          = eenBus;
		buschauffeur = eenBuschauffeur;
		eenBuschauffeur.setBusActor( this );
	}

	public void draw( Canvas canvas, WorldView worldView ) {
		bus.draw( canvas, worldView );
	}

	@Override
	public void handleCollision( World world, Actor actor ) {
		Assert.fail( "Programmers error, handleCollision() in the busWorld is not valid"  );
	}

	public void update( World world ) {
		long frameTime = System.currentTimeMillis();
		if ( world.getDeltaGametime() > 0.0f ) {
			while (    (        world.getGametime() - lastTime  ) > GAME_TIME_STEP_SIZE 
					&& ( System.currentTimeMillis() - frameTime ) < MAXIMUM_UPDATE_TIME_IN_MILLIS ) {
				if ( ! halteCommands.isEmpty() )
					execute( halteCommands.remove( 0 ) );
				else if ( ! commands.isEmpty() )
					execute( commands.remove( 0 ) );
				else {
					vulCommandosMetAlleRoutepuntenenHaltes();
					lastTime = world.getGametime();
				}
				lastTime += GAME_TIME_STEP_SIZE;
			}
		}
	}

	protected void addCommando( Locatie naarLocatie ) {
		commands.add( BusCommands.RIJ );
		locaties.add( naarLocatie );
	}

	protected void addCommando( HalteProxy halte ) {
		commands.add( BusCommands.HALTE );
		haltes.add( halte );
	}

	protected void addHalteCommando( BusCommands command, int wachttijd ) {
		EasyLog.v( "Executing bus stop command: " + command.name() );
		execute( command );
		while ( wachttijd-- > 0 )
			commands.add( 0, BusCommands.WACHT   );
	}

	protected void addHalteCommando( BusCommands command, int wachttijd,  Reiziger reiziger, HalteProxy halte ) {
		EasyLog.v( "Adding bus stop command: " + command.name() );
		commands .add( 0, command  );
		reizigers.add( 0, reiziger );
		haltes   .add( 0, halte    ); 
		while ( wachttijd-- > 0 )
			commands.add( 0, BusCommands.WACHT   );
	}


	protected int             getBusNummer()     { return bus.getNummer();        }
    protected int             getMaxPassagiers() { return bus.getMaxPassagiers(); }
    protected Locatie         getLocatie()       { return bus.getLocatie();       }
	protected boolean         isBusVol()         { return bus.isVol();            }
	protected boolean         isBusGestart()     { return bus.isGestart();        }
	protected boolean         isBusDeurOpen()    { return bus.isDeurOpen();       }
	protected double          getBusSnelheid()   { return bus.getSnelheid();      }
	protected List<Reiziger>  getBusPassagiers() { return bus.getPassagiers();    }
	protected void laatReizigerUitstappen( Reiziger reiziger, Halte halte ) {
		bus.remove( reiziger        );
		stapUit   ( reiziger, halte );
	}
	protected void laatReizigerInstappen ( Reiziger reiziger, Halte halte ) {
		bus.add   ( reiziger        );
		stapIn    ( reiziger, halte );
	}


	protected  void stapIn ( Reiziger reiziger, Halte halte ) { addHalteCommando( BusCommands.STAP_IN,     DEFAULT_WACHTTIJD, reiziger, route.getHalte( halte.getLocatie() ) ); }
	protected  void stapUit( Reiziger reiziger, Halte halte ) { addHalteCommando( BusCommands.STAP_UIT,    DEFAULT_WACHTTIJD, reiziger, route.getHalte( halte.getLocatie() ) ); }
	protected  void openDeur()                                { addHalteCommando( BusCommands.OPEN_DEUR,   DEFAULT_WACHTTIJD                  ); }
	protected  void sluitDeur()                               { addHalteCommando( BusCommands.SLUIT_DEUR , DEFAULT_WACHTTIJD                  ); }
	protected  void stop()                                    { addHalteCommando( BusCommands.STOP,        DEFAULT_WACHTTIJD * 4              ); }
	protected  void start()                                   { addHalteCommando( BusCommands.START,       DEFAULT_WACHTTIJD * 4              ); }
	protected  void versnel()                                 { addHalteCommando( BusCommands.VERSNEL,     DEFAULT_WACHTTIJD                  ); }
	protected  void rem()                                     { addHalteCommando( BusCommands.REM,         DEFAULT_WACHTTIJD                  ); }
	protected void handelHalteAf( HalteProxy halte ) {
		buschauffeur.handelHalteAf( new Halte( halte ) );
	}


	private void execute( BusCommands command ) {
		if ( command != BusCommands.RIJ &&  command != BusCommands.WACHT ) {
			EasyLog.v( "Executing command: " + command.name() );
		}
		switch( command ) {
		case RIJ:        bus.rijRichting( locaties.remove( 0 ) ); break;
		case HALTE:      handelHalteAf  (  haltes.remove( 0 ) );   break;
		case STAP_IN:    haltes.remove( 0 ).remove( reizigers.remove( 0 ) ); break;
		case STAP_UIT:   haltes.remove( 0 ).add   ( reizigers.remove( 0 ) ); break;
		case STOP:       bus.stop();      break;
		case START:      bus.start();     break;
		case OPEN_DEUR:  bus.openDeur();  break;
		case SLUIT_DEUR: bus.sluitDeur(); break;
		case VERSNEL:    bus.versnel();   break;
		case REM:        bus.rem();       break;
		case WACHT:      break;
		}
	}

	public void setRoute( Route eenRoute ) {
		route = eenRoute;
	}

	private void vulCommandosMetAlleRoutepuntenenHaltes() {
		for ( Locatie l : route.getRoutepunten() ) {
			rijNaar( l );
			if ( route.isHalte(l) ) {
				voegHalteCommandoToe( route.getHalte( l ) );
			}
		}
	}

	public void rijNaar( Locatie nieuweLocatie ) {
		while ( ! locatie.equals( nieuweLocatie ) ) {
			rijRichting( nieuweLocatie );
		}
	}

	public void rijRichting( Locatie nieuweLocatie )
	{
        Assert.check( ! locatie.equals( nieuweLocatie ), "Programmeerfout, oude en nieuwe locatie zijn hetzelfde"  );

        double verschilX = nieuweLocatie.x - locatie.x;
		double verschilY = nieuweLocatie.y - locatie.y;

		double afstand = Math.sqrt( verschilX * verschilX + verschilY * verschilY );

		double verplaatsingX = verschilX / afstand;
		double verplaatsingY = verschilY / afstand;

		if ( afstand > MAXIMUM_TE_RIJDEN_AFSTAND_PER_STAP ) {
			locatie = new Locatie( locatie.x + verplaatsingX, locatie.y + verplaatsingY );
		} else {
			locatie = nieuweLocatie;
		}

		voegRijCommandoToe( locatie );
	}
	
	private void voegRijCommandoToe  ( Locatie  naarLocatie ) { addCommando( naarLocatie ); }
	private void voegHalteCommandoToe( HalteProxy halte     ) { addCommando( halte       ); }

	public void  detectAndHandleCollision( World world, Actor actor ) {}

	public void setBuschauffeur( AbstracteBuschauffeur eenBuschauffeur ) {
		buschauffeur = eenBuschauffeur;
	}
}